﻿// MIT License
// 开源地址：https://gitee.com/co1024/AbcMvc
// Copyright (c) 2021-2023 1024
// Abc.Mvc=Furion+EF+SqlSugar+Pear layui admin.

using Elasticsearch.Net;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Elasticsearch;
using Serilog.Sinks.MSSqlServer;

namespace Abc.Utils;

public static class SerilogEx
{
    #region Serilog  日志

    public static readonly string SerilogOutputTemplate = "{NewLine}Date：{Timestamp:yyyy-MM-dd HH:mm:ss.fff} LogLevel：{Level}{NewLine}Message：{Message}{Properties:j}{NewLine}{NewLine}{Exception}" + new string('-', 50);

    /// <summary>
    /// 日志对象集合
    /// </summary>
    private static readonly ConcurrentDictionary<string, LogCacheItem<Serilog.ILogger>> Loggers = new();

    /// <summary>
    /// 获取 Serilog Logger
    /// </summary>
    /// <param name="logFloderName">日志文件夹名称</param>
    /// <param name="isLogEventLevelFloder">是否每个事件一个文件夹</param>
    /// <param name="rollOnFileSizeLimit">如果为true，则在达到文件大小限制时将创建一个新文件。文件名将以_NNN格式附加一个数字，第一个文件名没有数字。</param>
    /// <param name="minLogEventLevel">最低日志等级，默认Debug</param>
    /// <param name="rollingInterval">滚动间隔：日志将滚动到新文件的间隔</param>
    /// <param name="retainedFileCountLimit">保留的最大日志文件数，包括当前</param>
    /// <param name="flushToDiskInterval">TimeSpan 不为null，将定期在指定的时间执行完整磁盘刷新间隔。</param>
    /// <param name="buffered">指示是否可以缓存到输出文件。默认值 false</param>
    /// <param name="shared">允许多个进程共享日志文件。默认值为false</param>
    /// <param name="fileSizeLimitBytes">文件大小限制字节：允许日志文件访问的近似最大大小（以字节为单位）成长。对于不受限制的增长，传递null。默认值为1 GB。避免写作部分事件，限制内的最后一个事件将全部写入偶数如果超过限制</param>
    /// <param name="outputTemplate">日志输出模板</param>
    /// <param name="filename">文件名</param>
    /// <returns></returns>
    public static Serilog.ILogger GetLogger(
                                    // string path,
                                    // LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose,

                                    // LoggingLevelSwitch levelSwitch = null,

                                    string logFloderName = "default",//日志文件夹名称
                                    bool isLogEventLevelFloder = true,//是否按日志类别分类记录
                                                                      //ITextFormatter formatter,
                                    bool rollOnFileSizeLimit = true,//如果为true，则在达到文件大小限制时将创建一个新文件。文件名将以_NNN格式附加一个数字，第一个文件名没有数字。
                                                                    // Encoding encoding = null,
                                                                    // // FileLifecycleHooks hooks = null,
                                                                    //
                                                                    //
                                    LogEventLevel minLogEventLevel = LogEventLevel.Debug,//最低日志等级，默认Debug
                                    RollingInterval rollingInterval = RollingInterval.Hour,//滚动间隔：日志将滚动到新文件的间隔
                                    int? retainedFileCountLimit = null,//保留的最大日志文件数，包括当前
                                    TimeSpan? flushToDiskInterval = null,//TimeSpan 不为null，将定期在指定的时间执行完整磁盘刷新间隔。
                                    bool buffered = false,//指示是否可以缓存到输出文件。默认值 false
                                    bool shared = false,//允许多个进程共享日志文件。默认值为false
                                    long? fileSizeLimitBytes = 1073741824L,//文件大小限制字节：允许日志文件访问的近似最大大小（以字节为单位）成长。对于不受限制的增长，传递null。默认值为1 GB。避免写作部分事件，限制内的最后一个事件将全部写入偶数如果超过限制
                                    string outputTemplate = "",
                                    string filename = "")
    {
        //判断日志文件夹设置是否为空，如果为空，设置一个默认文件夹
        if (string.IsNullOrWhiteSpace(logFloderName))
        {
            logFloderName = "default";
        }
        else
        {
            logFloderName = logFloderName.Trim("\\");
        }
        //对自定义文件名进行处理，去除末尾的 反斜杠字符
        if (!string.IsNullOrWhiteSpace(filename))
        {
            filename = filename.Trim("\\");
        }
        //判断缓存的 ILogger 是否已存在，如果改以存在从字典集合直接取出使用，不在重新创建
        LogCacheItem<Serilog.ILogger> serilogCacheItem = null;
        var IsGetLogger = false;//是否从缓存中取出Logger对象
                                //if (Loggers.ContainsKey(logFloderName))
                                //{
        var state = Loggers.TryGetValue(logFloderName, out serilogCacheItem);
        if (state == true)
        {
            if (serilogCacheItem.Date == $"{DateTimeOffset.Now:yyyy-MM-dd}")
            {
                IsGetLogger = true;
            }
            else
            {
                //移除旧的Logger对象
                var removestate = Loggers.TryRemove(logFloderName, out var oldlogger);
            }
        }
        //}
        if (IsGetLogger == false)
        {
            outputTemplate = SerilogOutputTemplate;//日志输出模板
            if (!string.IsNullOrWhiteSpace(App.Configuration["Serilog:Customer:OutputTemplate"]))
            {
                outputTemplate = App.Configuration["Serilog:Customer:OutputTemplate"];
            }

            try
            {
                // if (string.IsNullOrWhiteSpace(outputTemplate)) outputTemplate = LogTemplate;
                //从配置文件取出配置数据
                //最低日志等级，默认Debug
                minLogEventLevel = App.Configuration["Serilog:Customer:MinLogEventLevel"]?.To<Serilog.Events.LogEventLevel>() ?? Serilog.Events.LogEventLevel.Debug;
                //滚动间隔：日志将滚动到新文件的间隔
                rollingInterval = App.Configuration["Serilog:Customer:RollingInterval"]?.To<Serilog.RollingInterval>() ?? Serilog.RollingInterval.Hour;
                //如果为true，则在达到文件大小限制时将创建一个新文件。文件名将以_NNN格式附加一个数字，第一个文件名没有数字。
                rollOnFileSizeLimit = App.Configuration["Serilog:Customer:RollOnFileSizeLimit"]?.To<bool>() ?? true;
                //指示是否可以缓存到输出文件。默认值 false
                buffered = App.Configuration["Serilog:Customer:Buffered"]?.To<bool>() ?? false;
                //允许多个进程共享日志文件。默认值为false
                shared = App.Configuration["Serilog:Customer:Shared"]?.To<bool>() ?? false;

                //文件大小限制字节：允许日志文件访问的近似最大大小（以字节为单位）成长。对于不受限制的增长，传递null。默认值为1 GB。避免写作部分事件，限制内的最后一个事件将全部写入偶数如果超过限制
                var tempFileSizeLimitBytes = App.Configuration["Serilog:Customer:FileSizeLimitBytes"];
                if (!string.IsNullOrWhiteSpace(tempFileSizeLimitBytes))
                {
                    fileSizeLimitBytes = tempFileSizeLimitBytes.To<long>();
                }
                //保留的最大日志文件数，包括当前
                var tempRetainedFileCountLimit = App.Configuration["Serilog:Customer:RetainedFileCountLimit"];
                if (!string.IsNullOrWhiteSpace(tempRetainedFileCountLimit))
                {
                    retainedFileCountLimit = tempRetainedFileCountLimit.To<int>();
                }
                //TimeSpan 不为null，将定期在指定的时间执行完整磁盘刷新间隔。
                var tempFlushToDiskInterval = App.Configuration["Serilog:Customer:FlushToDiskInterval"];
                if (!string.IsNullOrWhiteSpace(tempFlushToDiskInterval))
                {
                    flushToDiskInterval = tempFlushToDiskInterval.To<TimeSpan>();
                }
                //是否按日志类别分类记录
                isLogEventLevelFloder = App.Configuration["Serilog:Customer:IsLogEventLevelFloder"].To<bool>();
            }
            catch (Exception)
            {
            }
            if (isLogEventLevelFloder)//是否按日志类别分类记录
            {
                serilogCacheItem = new LogCacheItem<Serilog.ILogger>();
                serilogCacheItem.Logger = new Serilog.LoggerConfiguration()
                    .SetMinimumLevel(minLogEventLevel)
                    .SetWriteTo(logFloderName,//日志文件夹名称
                                              //ITextFormatter formatter,
                                              rollOnFileSizeLimit,
                                              // Encoding encoding = null,
                                              // FileLifecycleHooks hooks = null,
                                              minLogEventLevel,
                                              rollingInterval,
                                              retainedFileCountLimit,
                                              flushToDiskInterval,
                                              buffered,
                                              shared,
                                              fileSizeLimitBytes,
                                              outputTemplate,
                                              filename)
                    .CreateLogger();
            }
            else
            {
                //日志都记录到一个日志文件中
                serilogCacheItem = new LogCacheItem<Serilog.ILogger>();
                serilogCacheItem.Logger = new Serilog.LoggerConfiguration()
                                             .SetMinimumLevel(minLogEventLevel)
                                             .WriteTo.File($"logs\\{logFloderName}\\log.txt", rollingInterval: rollingInterval, rollOnFileSizeLimit: rollOnFileSizeLimit, buffered: buffered, shared: shared, fileSizeLimitBytes: fileSizeLimitBytes,
                                                 retainedFileCountLimit: retainedFileCountLimit, flushToDiskInterval: flushToDiskInterval, outputTemplate: outputTemplate)
                                             .CreateLogger();
            }
            Loggers.TryAdd(logFloderName, serilogCacheItem);
        }
        if (serilogCacheItem == null) serilogCacheItem.Logger = Serilog.Log.Logger;
        return serilogCacheItem.Logger;
    }

    private static Serilog.LoggerConfiguration SetWriteTo(this Serilog.LoggerConfiguration loggerConfiguration,// string path,
                                                                                                               // LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose,

                                    // LoggingLevelSwitch levelSwitch = null,
                                    string LogFloderName = "",//日志文件夹名称

                                    bool rollOnFileSizeLimit = true,//如果为true，则在达到文件大小限制时将创建一个新文件。文件名将以_NNN格式附加一个数字，第一个文件名没有数字。
                                                                    // Encoding encoding = null,
                                                                    // FileLifecycleHooks hooks = null,
                                    LogEventLevel minLogEventLevel = LogEventLevel.Debug,//最低日志等级，默认Debug
                                    RollingInterval rollingInterval = RollingInterval.Hour,//滚动间隔：日志将滚动到新文件的间隔
                                    int? retainedFileCountLimit = null,//保留的最大日志文件数，包括当前
                                    TimeSpan? flushToDiskInterval = null,//TimeSpan 不为null，将定期在指定的时间执行完整磁盘刷新间隔。
                                    bool buffered = false,//指示是否可以缓存到输出文件。默认值 false
                                    bool shared = false,//允许多个进程共享日志文件。默认值为false
                                    long? fileSizeLimitBytes = 1073741824L,//文件大小限制字节：允许日志文件访问的近似最大大小（以字节为单位）成长。对于不受限制的增长，传递null。默认值为1 GB。避免写作部分事件，限制内的最后一个事件将全部写入偶数如果超过限制
                                    string outputTemplate = "",
                                    string filename = "")
    {
        //日志是否输出到控制台
        var isConsole = App.Configuration["Serilog:Customer:IsConsole"]?.To<bool>() ?? false;
        if (isConsole)
        {
            loggerConfiguration.WriteTo.Console(outputTemplate: outputTemplate);
        }
        //日志是否写入到文件
        var isFile = App.Configuration["Serilog:Customer:IsFile"]?.To<bool>() ?? true;
        if (isFile)
        {
            foreach (Serilog.Events.LogEventLevel logEventLevel in Enum.GetValues(typeof(Serilog.Events.LogEventLevel)))
            {
                loggerConfiguration.WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == logEventLevel)
                    .WriteTo.File(LogFilePath(LogFloderName, logEventLevel, filename),
                        rollingInterval: rollingInterval, outputTemplate: outputTemplate,
                        rollOnFileSizeLimit: rollOnFileSizeLimit, fileSizeLimitBytes: fileSizeLimitBytes,
                        retainedFileCountLimit: retainedFileCountLimit, flushToDiskInterval: flushToDiskInterval,
                        shared: shared));
            }
        }
        //日志是否保存到MSSQL数据库
        var isMsSql = App.Configuration["Serilog:Customer:IsMsSql"]?.To<bool>() ?? false;
        if (isMsSql)
        {
            //var columnOpts = new ColumnOptions();
            //columnOpts.Store.Remove(StandardColumn.Properties);
            //columnOpts.Store.Add(StandardColumn.LogEvent);
            //columnOpts.LogEvent.DataLength = 2048;
            //columnOpts.PrimaryKey = options.TimeStamp;
            //columnOpts.TimeStamp.NonClusteredIndex = true;
            var columnOptions = new ColumnOptions();
            //连接字符串
            var connectionString = App.Configuration["Serilog:Customer:MSSQLSERVER:ConnectionString"];
            //表名
            var tableName = App.Configuration["Serilog:Customer:MSSQLSERVER:TableName"] ?? "SeriLogs";
            //    限制每个批将多少日志事件写入数据库（默认值：50）
            var batchPostingLimit = App.Configuration["Serilog:Customer:MSSQLSERVER:batchPostingLimit"]?.ToInt32() ?? 50;
            //记录日志最低等级
            var minimumLevel = App.Configuration["Serilog:Customer:MSSQLSERVER:restrictedToMinimumLevel"]?.To<LogEventLevel>() ?? LogEventLevel.Verbose;

            //选项
            var sinkOpts = new MSSqlServerSinkOptions
            {
                TableName = $"{tableName}-{DateTimeOffset.Now:yyyyMMdd}",
                BatchPostingLimit = batchPostingLimit,
                BatchPeriod = TimeSpan.FromSeconds(3),//执行时间间隔
                AutoCreateSqlTable = true
            };
            //写入到数据库
            loggerConfiguration.WriteTo.MSSqlServer(
                connectionString: connectionString,
                sinkOptions: sinkOpts,
                // tableName: tableName,
                columnOptions: columnOptions,
                // batchPostingLimit: batchPostingLimit,//批量插入数据库条数
                // period: TimeSpan.FromSeconds(5),//执行时间间隔
                restrictedToMinimumLevel: minimumLevel
            //autoCreateSqlTable: true
            );
        }

        return loggerConfiguration;
    }

    private static Serilog.LoggerConfiguration SetMinimumLevel(this Serilog.LoggerConfiguration loggerConfiguration, Serilog.Events.LogEventLevel minLevel)
    {
        switch (minLevel)
        {
            case LogEventLevel.Verbose://冗余的
                return loggerConfiguration.MinimumLevel.Verbose();

            case LogEventLevel.Debug://调试
                return loggerConfiguration.MinimumLevel.Debug();

            case LogEventLevel.Information://信息
                return loggerConfiguration.MinimumLevel.Information();

            case LogEventLevel.Warning://警告
                return loggerConfiguration.MinimumLevel.Warning();

            case LogEventLevel.Error://错误
                return loggerConfiguration.MinimumLevel.Error();

            case LogEventLevel.Fatal://致命
                return loggerConfiguration.MinimumLevel.Fatal();

            default:
                break;
        }
        return loggerConfiguration.MinimumLevel.Debug();
    }

    private static string LogFilePath(string LogFloderName, Serilog.Events.LogEventLevel loglevel, string filename = "")
    {
        var date = DateTime.Now.ToString("yyyy-MM-dd");//按时间创建文件夹
        if (!string.IsNullOrWhiteSpace(filename))
        {
            return $"logs\\{LogFloderName}\\{date}\\{filename}_.txt";
        }
        else
        {
            return $"logs\\{LogFloderName}\\{date}\\{loglevel:G}_.txt";
        }
    }

    /// <summary>
    /// 获取Serilog 自定义日志配置
    /// </summary>
    /// <returns></returns>
    public static void GetDefaultSerilogLoggerConfiguration(LoggerConfiguration config)
    {
        var date = DateTime.Now.ToString("yyyy-MM-dd");//按时间创建文件夹
        config.WriteTo.Console(outputTemplate: SerilogOutputTemplate)
              .WriteTo.File($"logs\\all\\{date}\\log_.txt", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 20971520, shared: true);
        foreach (Serilog.Events.LogEventLevel logEventLevel in Enum.GetValues(typeof(Serilog.Events.LogEventLevel)))
        {
            config.WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == logEventLevel)
              .WriteTo.File(LogFilePath("all", logEventLevel), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate, rollOnFileSizeLimit: true, fileSizeLimitBytes: 20971520, shared: true));
        }
    }

    #endregion Serilog  日志


    /// <summary>
    /// 添加SerilogTo Es日志拓展
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="isWriteFileLog">是否写入文件日志,默认false</param>
    /// <param name="configAction"></param>
    /// <returns></returns>
    public static IHostBuilder UseSerilogToEs(this IHostBuilder builder, bool isWriteFileLog = true, Action<LoggerConfiguration> configAction = null)
    {
        bool isSingleFileEnvironment = string.IsNullOrWhiteSpace(Assembly.GetEntryAssembly()!.Location);
        builder.UseSerilog(delegate (HostBuilderContext context, LoggerConfiguration configuration) {

            var _esLogConfigOptions = AppEx.GetConfig<EsLoggingConfigOptions>();
            #region Es日志
            //配置了ES Log才执行
            if (_esLogConfigOptions != null && _esLogConfigOptions.Urls != null && _esLogConfigOptions.Urls.Any())
            {
                var uris = _esLogConfigOptions.Urls?.Select(o => new Uri(o)) ?? new List<Uri>();

                //string environment = "Development";
                configuration
               .ReadFrom.Configuration(context.Configuration)
               .Enrich.FromLogContext()
               //过滤Net Core系统日志
               .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
               .WriteTo.Console()
               .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(uris)
               {
                   //OverwriteTemplate和TypeName一定要加，不然ES8无法写入日志

                   IndexFormat = _esLogConfigOptions.DefaultIndex,//默认索引名称
                                                                  //IndexFormat = $"{Assembly.GetExecutingAssembly().GetName().Name.ToLower().Replace(".", "-")}-{environment?.ToLower().Replace(".", "-")}-{DateTime.UtcNow:yyyy-MM}",
                   AutoRegisterTemplate = true,//是否ES日志自动注册一个索引模板。
                   OverwriteTemplate = true,//是否覆盖ES日志默认模板，ES8默认不支持写入，需要加此配置
                                            //TemplateName = "",//ES日志模板
                   FailureCallback = e => Console.WriteLine("Unable to submit event " + e.MessageTemplate),//日志发送失败触发事件
                   AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv8,//ES模板版本
                   TypeName = null,//ES8默认不支持写入，需要加此配置
                   MinimumLogEventLevel = LogEventLevel.Verbose,//最低日志等级
                   EmitEventFailure = EmitEventFailureHandling.RaiseCallback,//设置了当失败时调用FailureCallback
                   BatchAction = ElasticOpType.Create,
                   BatchPostingLimit = 50,//一次批量发送日志数量，默认50
                                          //ES认证，用户登录+是否开启SSL(Https)
                   ModifyConnectionSettings =
                        conn =>
                        {
                            //conn.BasicAuthentication("elastic", "123456");
                            SetBasicAuthentication(conn, _esLogConfigOptions); //设置基础安全认证
                            conn.ServerCertificateValidationCallback((source, certificate, chain, sslPolicyErrors) => false);
                            return conn;
                        }
               });

                //设置基础安全认证
                static void SetBasicAuthentication(ConnectionConfiguration conn, EsLoggingConfigOptions _esLogConfigOptions)
                {
                    if (!string.IsNullOrWhiteSpace(_esLogConfigOptions.AuthUserName) && !string.IsNullOrWhiteSpace(_esLogConfigOptions.AuthPassword))
                    {
                        conn.BasicAuthentication(_esLogConfigOptions.AuthUserName, _esLogConfigOptions.AuthPassword);
                    }
                }
            }
            #endregion

            #region 文件日志
            //如果打开了写文件日志或未配置的eslog
            if (isWriteFileLog || _esLogConfigOptions == null)
            {
                LoggerConfiguration loggerConfiguration = configuration.ReadFrom.Configuration(context.Configuration).Enrich.FromLogContext();
                if (configAction != null)
                {
                    configAction(loggerConfiguration);
                }
                else
                {
                    GetDefaultSerilogLoggerConfiguration(loggerConfiguration);
                }
                //else if (context.Configuration["Serilog:WriteTo:0:Name"] == null)
                //{
                //    loggerConfiguration.WriteTo.Console(LogEventLevel.Verbose, "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}").WriteTo.File(Path.Combine((!isSingleFileEnvironment) ? AppDomain.CurrentDomain.BaseDirectory : AppContext.BaseDirectory, "logs", "application..log"), LogEventLevel.Information, "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", null, retainedFileCountLimit: null, encoding: Encoding.UTF8, fileSizeLimitBytes: 1073741824L, levelSwitch: null, buffered: false, shared: false, flushToDiskInterval: null, rollingInterval: RollingInterval.Day);
                //}
            }
            #endregion
        });
        return builder;
    }

    /// <summary>
    /// 添加SerilogTo Es日志拓展
    /// </summary>
    /// <param name="builder"></param>
    /// <param name="isWriteFileLog">是否写入文件日志,默认false</param>
    /// <param name="configAction"></param>
    /// <returns></returns>
    public static WebApplicationBuilder UseSerilogToEs(this WebApplicationBuilder builder, bool isWriteFileLog = false, Action<LoggerConfiguration> configAction = null)
    {
        builder.Host.UseSerilogToEs(isWriteFileLog, configAction);
        return builder;
    }
}